嗨!讓我抓住禮拜天的尾巴,繼續研究Vue.js吧XD,之後就要分心處理React了,那廢話不多說,分隔線下進入正文!
首先提一下關於組件的事件處理,昨天有在文章中提到官方的一個例子:
HTML
<div id="demoCounter">
<button-counter></button-counter>
</div>
JavaScript
Vue.component('button-counter',{
data:()=>{
return {
count:0,
}
},
template:`<div>
<button @click="count++">Click me</button>
<span>目前點了{{count}}下</span>
</div>`
})
let demoCounter = new Vue({
el:'#demoCounter',
})
上方在設定組件時我們把事件寫再button
的@click
裡面,讓他被點擊時可以把count
加1,但是這僅僅於組件控制組件的資料而已,試著想想當把count
的值放到demoCounter
的data
中時,組件內的事件該怎麼控制count
加1呢?如果遇到這種情況可以使用Vue.js中的內建方法$emit
傳入事件名字,讓他對父層組件demoCounter
觸發用v-on
綁定的事件名稱:
HTML
<div id="demoCounter">
<!--再使用組件時設定組件內部監聽的$emit事件名稱來觸發父組件的事件-->
<button-counter @add-count="addCount"></button-counter>
<!--把count從內部組件移到外面-->
<span>目前點了{{count}}下</span>
</div>
JavaScript
Vue.component('button-counter',{
template:`<div>
<!--在組件內部使用v-on綁定$emit方法,傳入事件名稱-->
<button @click="$emit('add-count')">Click me</button>
</div>`,
})
let counterData = {
count:0,
}
let counterMethods = {
//被觸發的事件在這邊
addCount:()=>{
counterData.count+=1;
}
}
let demoCounter = new Vue({
el:'#demoCounter',
data:counterData,
methods:counterMethods,
})
如上方使用$emit
建立讓外層組件監聽的事件,就可以觸發並更改父組件的方法及資料。
然後這裡在使用事件的時候也有需要注意的地方,那就是$emit
傳入的方法名稱會全部變成小寫,而且和之前在設定組件名稱時不同,就算在這裡使用駝峰命名法
,他在外面v-on
的時候也不會幫你轉換成短橫線命名法
,也就是說駝峰式命名法
在這裡完全無用武之地,以下是例子:
HTML
<!--使用駝峰式命名法讓父組件監聽事件名稱-->
<button @click="$emit('addCount')">Click me</button>
<!--如果像上方$emit設定,以下兩種寫法都不會被觸發-->
<button-counter @add-count="addCount"></button-counter>
<button-counter @add-count="add-count"></button-counter>
<!--他會被轉成小寫的版本,所以只有以下寫法會被觸發-->
<button-counter @add-count="addcount"></button-counter>
------------------分隔線------------------
<!--如果一開始是短橫線命名法-->
<button @click="$emit('add-count')">Click me</button>
<!--不需另外轉換寫法也會被觸發-->
<button-counter @add-count="add-count"></button-counter>
所以說,官方在這裡註明了,不如就使用$emit
時只以短橫線命名法
處理,當然如果平時就是用短橫線命名法
就不需要在意這個了。
另一點是,有時候父組件上監聽的事件會需要一個或以上的參數傳入處理,所以$emit
他第一個參數如上面所說是呼叫父組件的事件名稱,這邊也要感謝fysh711426大大留言補充關於參數的部分,如果是由$emit
指定事件,並額外傳入參數給父組件的話,$emit
會把第二個以後的全部參數都放到arguments
這個物件中,也就是說父組件如果要獲取子組件藉由$emit
傳的參數,必須使用arguments[0]
代表第一個參數、arguments[1]
代表第二個參數..等等,我們看下方例子:
HTML
<div id="demoCounter">
<!--在父組件上用arguments來接收子組件傳的參數-->
<button-counter @add-count="addCount(arguments)"></button-counter>
<span>目前點了{{count}}下</span>
</div>
JavaScript
Vue.component('button-counter',{
template:`<div>
<!--在組件內部使用v-on綁定$emit方法,傳入事件名稱外多傳了兩個參數-->
<button @click="$emit('add-count','傳入一個參數','傳入第二個參數')">Click me</button>
</div>`,
})
let counterData = {
count:0,
}
let counterMethods = {
addCount:(arrArg)=>{
//在事件內分別印出arrArg物件,和第一個值還有第二個值
console.log(arrArg)
console.log(arrArg[0])
console.log(arrArg[1])
counterData.count+=1;
}
}
let demoCounter = new Vue({
el:'#demoCounter',
data:counterData,
methods:counterMethods,
})
結果會如下:
接著來提好久不見的v-model
吧!
v-model
之前的文章中有說過,v-model
其實是個語法糖,也就是讓各位大大用起來覺得甜甜的(咦?),先複習一下吧!v-model
到底幫我們做了哪些事:
<input v-model="message" />
會等於:
<input :value="message" @input="message = $event.target.value" />
上面的觀念之前的文章中有提過(還不熟的可以看這裡),所以應該不會有太大的問題,但在組件上直接使用v-model
是不會有任何作用的,所以當我們知道v-model
的原理以後,也可以用相同的方式放到讓v-model
在組件上實現。
但是一口氣說完,第一個想法可能是「天啊!這是什麼鬼?」,所以這個部分讓我們大部分解一下過程:
Props
屬性,並用指定value
來綁定input
的value
值,有沒有初始值都沒關係,但是為了做到v-model
的v-bind:value="value"
必須這麼做。Vue.component('inputName',{
//用props屬性來為組件內的DOM綁定資料
props:['value'],
template:`<div>
<!--這邊的觀念是:value="message"的部分-->
<input :value='value' >
</div>`,
})
v-model
幫我們做的第二件事情@input="message = $event.target.value"
,但是我們v-model
綁定的資料在父組件的data
中,所以用上面學到的$emit
來控制和觸發父組件的事件,而上面也有說過$emit
的第一個參數為觸發父組件的事件名稱,那我們的事件名稱是什麼?先回頭看一下HTML
的部分:<div id="demoInput">
<!--在組件裡用v-model綁定父組件data中的nameVal-->
<input-name v-model="nameVal"></input-name>
<span>我的名字是:{{nameVal}}</span>
</div>
單就<input-name v-model="nameVal"></input-name>
這一行,大家應該可以知道他做了哪些事情吧?他會等於:
HTML
<input-name :value="nameVal" @input="nameVal=$event.target.value"></input-name>
欸嘿嘿!聰明的你發現父組件的事件了嗎?沒錯!就是input
,所以$emit
要綁定的事件名稱就是input
,但是光是呼叫到input
還是不夠的,因為父組件的input
會用$event.target.value
的值去指定給nameVal
,所以在事件後方要再多加一個參數,讓父組件的input
接收,所以設定第二個參數為input
的值:$event.target.value
。
JavaScript
Vue.component('inputName',{
props:['value'],
template:`<div>
<!--加上了@input="message = $event.target.value"的觀念,並用$emit來觸發父組件的input事件-->
<input :value='value' @input="$emit('input', $event.target.value)">
</div>`,
})
//這裡是父組件,綁定的資料在data中
let demoInput = new Vue({
el:'#demoInput',
data:{
nameVal:'',
},
})
最後的程式碼,把所有註解拿掉會如下方這樣子,也完成了在組件中實做v-model
,雖然看起來沒有幾行,但是卻用到了之前提到的不少觀念,所以如果有不懂得可以看一下前面的幾篇文章,或是自己練習看看,都會加深一點印象!
HTML
<div id="demoInput">
<input-name v-model="nameVal"></input-name>
<span>我的名字是:{{nameVal}}</span>
</div>
JavaScript
Vue.component('inputName',{
props:['value'],
template:`<div>
<input :value='value' @input="$emit('input', $event.target.value)">
</div>`,
})
let demoInput = new Vue({
el:'#demoInput',
data:{
nameVal:'',
},
})
得到的結果就和我們一般使用的<input v-model="nameVal" />
一樣對吧!但沒想到加入組件後會變的那麼難懂,不過還是老話一句,熟能生巧,雖然感覺我發文很頻繁,不過每次在寫範例的時候還是會一直偷看之前自己發的文XD。
最後感謝大大們的觀看!如果文章中有任何解釋不清楚或理解錯誤的地方,還請各位大大留言告訴我,另外有任何想法也都歡迎留言和我討論,不管是什麼我都會認真看過的!謝謝大家
組件的 v-model
原理講的好詳細。
不過子組件的 props 應該是綁 value
才對,要同屬性名。
Vue.component("inputName",{
props:['value'],
template:`
<input :value="value" :input="$emit('input', $event.target.value)">`
子組件應該是利用 arguments[0]
取得 $emit
傳進來的參數。
<input-name v-model='nameVal'></input-name>
//等於
<input-name :value='nameVal' :input='nameVal=arguments[0]'></input-name>
參考資料
[1] Vue组件中的v-model实现原理分析
[2] v-model原理及自定义组件上的实现
哇!感謝大大分享研究內容!
不過實做v-model
的時候,
子組件的props
應該不用特別和屬性名相同吧?
因為那只是為了綁定子組件自身的資料,讓他可以綁定v-model
我剛剛試著把props
改成data
也是能達到一樣的效果:
Vue.component('inputName',{
data:()=>{
return {
val:""
}
},
template:`<div>
<input :value='val' @input="$emit('input', $event.target.value)">
</div>`,
})
有錯再麻煩大大告訴我
另外$emit
傳的參數是用arguments[0]
去接這我真的疏忽了!我會再把他補進文章中,很感謝大大還特別去找資料!
您試試看將 nameVal:'abc'
給預設值看看,觀察 input 有沒有接到值,
props 是用來接收外層 attr
傳入的值,一般 attr 可以自訂,不過 v-model 預設的 attr 剛好是 value。
<input-name :value='nameVal' :input='nameVal=arguments[0]'>
我們如果自訂一個,應該也是可以。
<input-name v-model='nameVal' :val='nameVal'>
換成 data 就變成內部變數了,就不能由外部給子組件預設值。
哈哈,對的!
其實如果v-bind
是綁定props
內的值,那就可以由外部去操控組件內的預設值,這點value
和val
應該都可以做到。
不過換個角度想如果用data
代替props
的話,預設值只需要直接指定給data
內的val
就可以了吧!
我也還沒在實務上處理過,不知道哪種方法是目前較常用的寫法,但是直接使用value
,就能夠讓畫面看起來簡潔一點,畢竟v-model
都幫我們處理:value
了,其實官方文件也是用value
,只是我天生反骨想說把他改掉看看
感謝大大耐心說明!
預設值通常需要由外部傳入,例如資料需要用 ajax 取得,因此 props 是比較適合的。
props
像組件對外的接口。data
則是組件內部自己的狀態。外部並不能直接改變組件內 data 的值,只能間接的透過 props 將資料傳入,
且 props 是唯讀的,所以組件內也不能通過修改 props 影響外部。
這樣的好處是組件的作用域獨立,且資料流是單向的,只會從根節點傳向子節點,這是 Vue 相比 ng1 改進的地方。
我也沒有實務上用過。
我才是完全沒有用過前端框架的人,很感謝大大分享經驗和討論,不過就像大大說的,讓資料的來源都屬於單向會比較好,不會有的放在props
有些放在data
,感覺也會很亂而且不好維護。
主要是模組化的開發模式現在還不熟,這也是我要學習的地方
之後在這個系列的最後我會做一個todolist
,現在對這些屬性還是很模糊,因為真得很方便XD,不過先試著把官方文件讀完,到時候說不定會有恍然大悟的感覺
大大我有把參數的部分更新上去了!
另外我還是把v-model
的部分改為和官方的例子一樣,避免誤會
這次竟然沒有繼續反骨。
是指接收參數的arguments
部分嗎?
其實我也有嘗試改成其他單字,但不能用
改為和官方的例子一樣,避免誤會
這句話,哈哈哈
我學乖了啊
想請問一下,關於HTML
<input-name v-model="nameVal"></input-name>
如果不用v-model,上一行會等於
<input-name :value="nameVal" @input="nameVal=$event.target.value"></input-name>
不過在實際測試上
HTML好像要改成
<input-name :value="nameVal" @input="nameVal=$event"></input-name>
才能運行,不知道有沒有遇到同樣的情況?